package cn.sytton.taffecloud.common.minio;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.sytton.taffecloud.common.minio.config.MinioConfig;
import cn.sytton.taffecloud.common.minio.config.MinioPolicy;
import cn.sytton.taffecloud.common.minio.constants.BucketType;
import cn.sytton.taffecloud.common.minio.util.OutputStreamUtil;
import cn.sytton.taffecloud.common.model.file.FileEntity;
import cn.sytton.taffecloud.common.model.file.FileStreamEntity;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Minio工具
 *
 * @author skyrock
 */
@Slf4j
@Component
public class MinioService {

    private final static long WORKER_ID = 1L; // 多服务节点部署时，可把此参数配置成SERVICE_ID

    private final static long DEFAULT_EXPIRY_MINUTE = 1440L; //默认访问链接24小时过期

    @Resource
    private MinioConfig minioConfig;

    @Resource
    private MinioClient minioClient;

    /**
     * 查看存储bucket是否存在
     * @return boolean
     */
    public Boolean bucketExists(String bucketName) {
        try {
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 创建存储bucket
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 创建Public存储bucket
     * @return Boolean
     */
    public Boolean makePublicBucket(String bucketName) {
        try {
            makeBucket(bucketName);
            minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(MinioPolicy.setPublic(bucketName)).build());
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 删除存储bucket
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取全部bucket
     */
    public List<Bucket> getAllBuckets() {
        try {
            return minioClient.listBuckets();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }


    /**
     * 文件上传
     *
     * @param file 文件
     * @return MinioFile 返回对象
     */

    /**
     * 文件上传
     * @param is 文件流
     * @param size 大小
     * @param name 文件名
     * @param bucketName 桶
     * @return 文件实体
     */
    public FileEntity upload(InputStream is, long size, String name, String bucketName) {
        String objectName = StrUtil.format("{}/{}_{}_{}.{}",
                DateUtil.format(new Date(), "yyyy/MM/dd"),
                FileNameUtil.getPrefix(name),
                IdUtil.getSnowflake(WORKER_ID).nextIdStr(),
                WORKER_ID,
                FileNameUtil.getSuffix(name));
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName)
                    .stream(is, is.available(), -1).contentType(ContentType.OCTET_STREAM.toString()) .build();
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }

        return new FileEntity(
                bucketName,
                objectName,
                name,
                FileNameUtil.getSuffix(name),
                size,
                getUrl(objectName, bucketName),
                getPublicUrl(objectName, bucketName));
    }

    /**
     * 文件上传
     * @param file 文件
     * @param bucketName 桶
     * @return 文件实体
     */
    public FileEntity upload(File file, String bucketName) {
        return upload(IoUtil.toStream(file), file.length(), file.getName(), bucketName);
    }

    /**
     * 文件上传
     *
     * @param file 文件
     * @return MinioFile 返回对象
     */
    public FileEntity upload(MultipartFile file, String bucketName) {
        String objectName = StrUtil.format("{}/{}_{}_{}.{}",
                            DateUtil.format(new Date(), "yyyy/MM/dd"),
                            FileNameUtil.getPrefix(file.getOriginalFilename()),
                            IdUtil.getSnowflake(WORKER_ID).nextIdStr(),
                            WORKER_ID,
                            FileNameUtil.getSuffix(file.getOriginalFilename()));
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
        return new FileEntity(
                bucketName,
                objectName,
                file.getOriginalFilename(),
                FileNameUtil.getSuffix(file.getOriginalFilename()),
                file.getSize(),
                getUrl(objectName, bucketName),
                getPublicUrl(objectName, bucketName));
    }

    /**
     * 获取资源永久访问链接, (如果bucketName是private的，永久链接无法访问)
     * @param objectName
     * @param bucketName
     * @return
     */
    public String getPublicUrl(String objectName, String bucketName) {
        return minioConfig.getEndpoint()+"/"+bucketName+"/"+objectName;
    }

    /**
     * 获取资源临时访问链接, 默认24小时过期
     * @param objectName
     * @param bucketName
     * @return
     */
    public String getUrl(String objectName, String bucketName) {
        return getUrl(objectName, bucketName, DEFAULT_EXPIRY_MINUTE);
    }

    /**
     * 获取资源临时访问链接
     * @param objectName
     * @param bucketName
     * @param expiryMinute
     * @return
     */
    public String getUrl(String objectName, String bucketName, long expiryMinute) {
        try {
            return minioClient.getPresignedObjectUrl(
                            GetPresignedObjectUrlArgs.builder()
                                    .method(Method.GET)
                                    .bucket(bucketName)
                                    .object(objectName)
                                    .expiry((int)TimeUnit.MINUTES.toSeconds(expiryMinute))
                                    .build());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return "";
        }
    }

    /**
     * 文件下载
     * @param bucketName 桶
     * @param objectName 存储文件名
     * @param res response
     */
    public void download(String bucketName, String objectName, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName)
                .object(objectName).build();
        download(objectArgs, objectName, res);
    }

    /**
     * 文件下载
     * @param file MinioFile
     * @param res HttpServletResponse
     */
    public void download(FileEntity file, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(file.getBucketName())
                .object(file.getObjectName()).build();
        String fileName = StringUtils.hasText(file.getOriginName()) ? file.getOriginName() : file.getObjectName();
        download(objectArgs, fileName, res);
    }

    private void download(GetObjectArgs objectArgs, String downloadName, HttpServletResponse res) {
        try (GetObjectResponse response = minioClient.getObject(objectArgs)){
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
                while ((len=response.read(buf))!=-1){
                    os.write(buf,0,len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();

                OutputStreamUtil.setDownloadHeader(res, downloadName, bytes.length);
                try (ServletOutputStream stream = res.getOutputStream()){
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public void downloadZip(List<FileEntity> files, HttpServletResponse res, String name) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ZipOutputStream zos = new ZipOutputStream(bos);

            for (FileEntity file : files) {
                GetObjectArgs args = GetObjectArgs.builder().object(file.getObjectName()).bucket(file.getBucketName()).build();
                GetObjectResponse objResp = minioClient.getObject(args);
                byte[] buf = new byte[1024];
                int len;
                FastByteArrayOutputStream os = new FastByteArrayOutputStream();
                while ((len = objResp.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();

                //写入ZipEntry
                zos.putNextEntry(new ZipEntry(file.getOriginName()));
                zos.write(os.toByteArray());
                zos.closeEntry();
            }
            zos.close();
            byte[] bytes = bos.toByteArray();

            String fileName = (StringUtils.hasText(name) ? name : DateUtil.format(new Date(), "yyyyMMddHHmmss"))+".zip";
            OutputStreamUtil.setDownloadHeader(res, fileName, bytes.length);
            ServletOutputStream stream = res.getOutputStream();
            stream.write(bytes);
            stream.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    private FileStreamEntity getFile(FileEntity file) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(file.getBucketName())
                .object(file.getObjectName()).build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)){
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
                while ((len=response.read(buf))!=-1){
                    os.write(buf,0,len);
                }
                os.flush();
                return new FileStreamEntity(os, file.getOriginName());
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }
    /**
     * 查看文件对象
     * @param bucketName 存储bucket名称
     * @return 存储bucket内文件对象信息
     */
    public List<FileEntity> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        List<FileEntity> objectItems = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                Item item = result.get();
                objectItems.add(new FileEntity(bucketName, item.objectName(),item.size()));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
        return objectItems;
    }

    /**
     * 批量删除文件对象
     * @param bucketName 存储bucket名称
     * @param objects 对象名称集合
     */
    public Iterable<Result<DeleteError>> removeObjects(String bucketName, String... objects) {
        List<DeleteObject> dos = Arrays.stream(objects).map(DeleteObject::new).collect(Collectors.toList());
        return minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(dos).build());
    }

    /**
     * 判断并创建存储桶
     * @param type 类别
     * @param name 名称
     */
    public void makeBucket(BucketType type, String name) {
        if (!bucketExists(name)) {
            if (type == BucketType.PRIVATE) {
                makeBucket(name);
            }
            if (type == BucketType.PUBLIC) {
                makePublicBucket(name);
            }
        }
    }
}
